/*!
 * Author:   Roman Belte
 */

(function( $ ) {

    var userAgent = navigator.userAgent;
	
	var isAndroid = /Android/i.test( userAgent ),
		isIEMobile = /IEMobile/i.test( userAgent ),
		isBlackBerry = /BlackBerry/i.test( userAgent ),
		isBlackBerry5 = /BlackBerry\d{4}\/5\.0\.0/i.test( userAgent );
		
	var pasteEvent = $.browser.msie || isIEMobile ? 'paste' : 'input';
	
	var defaults = {
        namespace: 'mask',
        selector: 'input[mask]',
        attributes: {
            mask: 'mask',
            holder: 'maskholder'
        },
        rules: {
            '9': "[0-9]",
            'a': "[A-Za-z]",
            '*': "[A-Za-z0-9]",
			'1': "[0-1]",
			'2': "[0-2]",
			'3': "[0-3]",
			'5': "[0-5]",
        },
        catchBackSpace: isBlackBerry && !isBlackBerry5,
		fixT9withTimer: true
    };

    
    //////////////////////// Caret functions ///////////////////////
    var getCaret = function( input ) {
        var range, start, end;
        if ( input.setSelectionRange ) {
            start = input.selectionStart;
            end = input.selectionEnd;
        }
        else if ( document.selection ) {
            range = document.selection.createRange();
            start = -range.duplicate().moveStart( 'character', -100000 );
            end = start + range.text.length;
        }
		return { start: start, end: end };
    };

    var setCaret = function( input, index ) {
		if ( isBlackBerry5 ) {
			$( input ).attr({ attrForDelay: 'true' });
			$( input ).removeAttr( 'attrForDelay' );
		}
		if ( input.setSelectionRange ) {
            input.setSelectionRange( index, index );
        }
        else if ( input.createTextRange ) {
            var range = input.createTextRange();
            range.collapse( true );
            range.moveEnd( 'character', index );
            range.moveStart( 'character', index );
            range.select();
        }
    };
	
	var setCaretAfterDelay = function( input, index, delay ) {
		delay || delay === 0 ?
			setTimeout(function() {
				setCaret( input, index );
			}, delay ) :
			setCaret( input, index );
	};

	
	////////////////////////// Mask plugin /////////////////////////
    $.fn.mask = function( opts ) {
        var opts = $.extend( {}, defaults, opts ),
            namespace = opts.namespace,
            attributes = opts.attributes,
            interval, interval2;

        this.delegate( opts.selector, 'focusin focusout', function( e ) {
            var input = this, $input = $( input ),
                value = input.value, length = input.value.length,
                holder = $.trim( $input.attr( attributes.holder ) ) || '_',
                mask = $.trim( $input.attr( attributes.mask ) ) || '',
                data = $input.data( namespace ), buffer = [ ], tests = [ ],
                ignoreKey = false, validKey = false, result = false,
                caretPos = { start: 0 }, len = mask.length, offset = 0;

            clearInterval( interval );
            clearInterval( interval2 );

            if ( mask && ! input.readOnly ) {
                
                /*************** Initialize ****************/
                if ( ! data ) {
                    data = { tests: tests, buffer: buffer };
                    $input.data( namespace, data );
					value = value.replace( /\D/g, '' );

                    for ( var i = 0; i < len; i++ ) {
                        var ch = mask.charAt( i ), ch2;
                        if ( opts.rules.hasOwnProperty( ch ) ) {
                            ch2 = value.charAt( i - offset );
                            tests[ i ] = new RegExp( opts.rules[ ch ] );
							buffer[ i ] = tests[ i ].test( ch2 ) ? ch2 : holder;
                        }
                        else {
                            buffer[ i ] = ch;
                            tests[ i ] = null;
							if ( ch != value.charAt( i ) ) {
								offset++;
							}
                        }
                    }
                }
                else {
                    tests = data.tests;
                    buffer = data.buffer;
                }

                if ( e.type == 'focusin' ) {
                    var seekNext = function( pos ) {
                        while ( ++pos <= len && ! tests[ pos ] );
                        return pos;
                    };

                    var seekPrev = function( pos ) {
                        while ( --pos >= 0 && ! tests[ pos ] );
                        return pos;
                    };
                    
                    var clearBuffer = function( start, end ) {
                        for ( var i = start; i < end && i < len; i++ ) {
                            if ( tests[ i ] ) {
                                buffer[ i ] = holder;
                            }
                        }
                    };
					
                    input.value = buffer.join( '' );
                    input.maxLength = mask.length + 1;
					length = input.value.length;

                    if ( ! value ) {
                        setCaretAfterDelay( input, 0, 1 );
                    }

                    /******* BlackBerry Backspace issue ********/
                    if ( opts.catchBackSpace ) {
                        interval = setInterval(function() {
                            if ( input.value.length < length ) {
                                var caret = getCaret( input ),
                                    start = caret.start,
                                    end = length - input.value.length,
                                    pos = start;

                                if ( end > 1 ) {
                                    end += start;
                                }
                                else {
                                    end = seekNext( start );
                                    start = seekPrev( start + 1 );
                                    pos = seekPrev( start + 1 );
                                }

                                caretPos = { start: pos };
                                clearBuffer( start, end );
                                input.value = buffer.join( '' );
                                setCaret( input, pos );
                            }
                            length = input.value.length;
                        }, 2 );
                    }

                    /************** T9 issues ******************/
                    if ( opts.fixT9withTimer ) {
                        interval2 = setInterval(function() {
                            if ( input.value != value ) {
                                if ( ! validKey || isAndroid || isIEMobile ) {
                                    var caret = caretPos;
                                    if ( ! caret ) {
                                        caret = getCaret( input );
                                    }
                                    input.value = buffer.join( '' );
                                    setCaret( input, caret.start );
                                }
                                validKey = false;
                                value = input.value;
                            }
                        }, 5 );
                    }

                    /************ Keyboard Events **************/
                    $input.unbind( '.' + namespace );
                    
                    $input.bind( 'keydown.' + namespace, function( e ) {
                        var key = e.which,
                            caret = getCaret( this ),
                            start = caret.start, end = caret.end, pos = start;

                        ignoreKey = false;

                        if ( key == 8 || key == 46 ) {
                            validKey = true;
                            ignoreKey = true;

                            if ( end - start == 0 ) {
                                start = key != 46 ? seekPrev( start ) : ( end = seekNext( start - 1 ) );
                                end   = key == 46 ? seekNext( end ) : end;
                                pos   = seekPrev( start + 1 );
                            }

                            clearBuffer( start, end );
                            this.value = buffer.join( '' );
                            caretPos = { start: pos < 0 ? 0 : pos };
							setCaretAfterDelay( this, pos, isBlackBerry5 );

                            return false;
                        }
                    });

                    $input.bind( 'keypress.' + namespace, function( e ) {
                        var key = e.which,
                            caret = getCaret( this ),
                            metaKey = e.ctrlKey || e.altKey || e.metaKey;

                        if ( key && key >= 32 && ! metaKey ) {
                            var p = seekNext( caret.start - 1 );
                            if ( p < len ) {
                                var ch = String.fromCharCode( key );
                                if ( tests[ p ].test( ch ) ) {
                                    validKey = true;
                                    buffer[ p ] = ch;
                                    this.value = buffer.join( '' );
                                    caretPos = { start: seekNext( p ) };
                                    setCaretAfterDelay( this, caretPos.start, isBlackBerry );
                                }
                                else if ( isBlackBerry ) {
                                    setCaretAfterDelay( this, caret.start, 0 );
                                }
                            }
                            return false;
                        }
                        else if ( ignoreKey ) {
							if ( isBlackBerry5 ) {
								setCaretAfterDelay( this, caretPos.start, 1 );
							}
                            return false;
                        }
                    });
					
					/************ OnPaste event ****************/
					$input.bind( pasteEvent + '.' + namespace, function( e ) {
						return false;
					});
					
					
					/**** BlackBerry 5.0.0.xxx KeyBoardEvent ***/
					if ( isBlackBerry5 ) {
						$input.bind( 'textInput.' + namespace, function( e ) {
							var ch = e.originalEvent.data,
								caret = getCaret( this );

							if ( ch ) {
								var p = seekNext( caret.start - 1 );
								if ( p < len ) {
									if ( tests[ p ].test( ch ) ) {
										buffer[ p ] = ch;
										this.value = buffer.join( '' );
										caretPos = { start: seekNext( p ) };
										setCaretAfterDelay( this, caretPos.start, 1 );
									}
								}
							}
						});
					}

                }
                else if ( e.type == 'focusout' ) {
                    $input.unbind( '.' + namespace );
                        
                    $.each( tests, function( i, reg ) {
                        if ( reg && reg.test( value.charAt( i ) ) ) {
                            result = true;
                            return false;
                        }
                    });

                    if ( ! result ) {
                        input.value = '';
                    }
                }

            }
        });

        return this;
    };

})( jQuery );